/**
 * 
 */
package com.acme.bankapp.network;

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.regex.Pattern;

import javax.security.auth.login.AccountNotFoundException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.acme.bankapp.account.AccountState;
import com.acme.bankapp.account.impl.CheckingAccount;
import com.acme.bankapp.account.impl.SavingAccount;
import com.acme.bankapp.domain.bank.Account;
import com.acme.bankapp.domain.bank.Bank;
import com.acme.bankapp.domain.bank.BankException;
import com.acme.bankapp.domain.bank.client.Client;
import com.acme.bankapp.domain.bank.client.ClientExistsException;
import com.acme.bankapp.domain.bank.client.ClientNotFoundException;
import com.acme.bankapp.domain.bank.client.Gender;
import com.acme.bankapp.service.bank.BankService;

/**
 * Class implements all business logic for working with Bank.
 * @author Alekov Andrey
 *
 */
public class DefaultBankApplicationImpl implements BankApplication{
	final static Logger logger = LoggerFactory.getLogger(DefaultBankApplicationImpl.class);
	private Bank bank = new Bank("OnlineBank");
	private BankService bankService = new BankService();
	final static String fieldSeparator = ";";
	/**
	 * Send message to client
	 * @param message
	 * @param out
	 */
	public void sendMessage(Message message, ObjectOutputStream out){
		try {
			logger.info(">> " + message.toString());
			out.writeObject(message);
			out.flush();
		} catch (IOException e) {
			logger.error(e.getMessage());
			e.printStackTrace();
		}
	}
	
	@Override
	public void onClientAdd(String messageValue, ObjectOutputStream out) {
		logger.debug("Try to add new client to bank");
		try {
			String[] args = messageValue.split(Pattern.quote(fieldSeparator));
			if (args[1].equals("MALE")){
				bankService.addClient(bank, new Client(args[0], Gender.MALE));
			}
			else {
				bankService.addClient(bank, new Client(args[0], Gender.FEMALE));
			}
			sendMessage(new Message("OK", "Client added to bank"), out);
		} catch (ClientExistsException e){
			logger.error(e.getMessage());
			sendMessage(new Message("ERROR", e.getMessage()), out);
		} catch (ArrayIndexOutOfBoundsException e){
			logger.error("Invalid message format");
			sendMessage(new Message("FAIL", "Invalid message format"), out);
		}
	}

	@Override
	public void onClientGet(String messageValue, ObjectOutputStream out) {
		logger.debug("Try to get information about client by name");
		try {
			String[] args = messageValue.split(Pattern.quote(fieldSeparator));
			Client client = findClient(args);
			sendMessage(new Message("OK", client.toString()), out);
		} catch (ArrayIndexOutOfBoundsException e) {
			logger.error("Invalid message format");
			sendMessage(new Message("FAIL", "Invalid message format"), out);
		} catch (ClientNotFoundException e) {
			logger.info("Client not found");
			sendMessage(new Message("FAIL", e.getMessage()), out);
		}
	}

	@Override
	public void onClientChange(String messageValue, ObjectOutputStream out) {
		logger.debug("Try to change client information");
		try {
			String[] args = messageValue.split(Pattern.quote(fieldSeparator));
			Client client = findClient(args);
			client.setName(args[1]);
			if (args[2].equals("MALE")){
				client.setGender(Gender.MALE);
			}
			else {
				client.setGender(Gender.FEMALE);
			}
			sendMessage(new Message("OK", client.toString()), out);
		} catch (ArrayIndexOutOfBoundsException e) {
			logger.error("Invalid message format");
			sendMessage(new Message("FAIL", "Invalid message format"), out);
		} catch (ClientNotFoundException e) {
			logger.info("Client not found");
			sendMessage(new Message("FAIL", e.getMessage()), out);
		}
	}

	@SuppressWarnings("deprecation")
	@Override
	public void onAccountAdd(String messageValue, ObjectOutputStream out) {
		logger.debug("Try to add new account");
		try {
			String[] args = messageValue.split(Pattern.quote(fieldSeparator));
			Client client = findClient(args);
			if (args[1].equals("SAVING")){
				client.addAccount(new SavingAccount(new Double(args[2])));
			}
			else{
				client.addAccount(new CheckingAccount(new Double(args[2]), 
						new Double(args[3])));
			}
			sendMessage(new Message("OK", client.getAccounts().toString()), out);
		} catch (ArrayIndexOutOfBoundsException e){
			logger.error("Invalid message format");
			sendMessage(new Message("FAIL", "Invalid message format"), out);
		} catch (ClientNotFoundException e) {
			logger.info("Client not found");
			sendMessage(new Message("FAIL", e.getMessage()), out);
		}
	}

	@Override
	public void onAccountChange(String messageValue, ObjectOutputStream out) {
		logger.debug("Try to change account state");
		try {
			String[] args = messageValue.split(Pattern.quote(fieldSeparator));
			Client client = findClient(args);
			Account account = client.getAccount(new Long(args[1]));
			if (args[2].equals("CLOSED")){
				account.setState(AccountState.CLOSED);
			}
			else{
				account.setState(AccountState.OPENED);
			}
			sendMessage(new Message("OK", client.getAccounts().toString()), out);
		} catch (ArrayIndexOutOfBoundsException | AccountNotFoundException |
				NumberFormatException  e){
			logger.error(e.getMessage());
			sendMessage(new Message("FAIL", "Invalid message format"), out);
		} catch (ClientNotFoundException e) {
			logger.info("Client not found");
			sendMessage(new Message("FAIL", e.getMessage()), out);
		}
	}


	@Override
	public void onAccountDeposit(String messageValue, ObjectOutputStream out) {
		logger.debug("Try to deposit account");
		try{
			String[] args = messageValue.split(Pattern.quote(fieldSeparator));
			Client client = findClient(args);
			for (Account account : client.getAccounts()) {
				if (account.getAccountId() == new Long(args[2])){
					bankService.modifyBank(bank, client, account.getAccountId(), 
							new Double(args[3]));
				}
			}
		} catch (AccountNotFoundException | BankException e){
			logger.info(e.getMessage());
			sendMessage(new Message("FAIL", e.getMessage()), out);
		} catch (NumberFormatException e) {
			logger.error(e.getMessage());
			sendMessage(new Message("FAIL", "Invalid message format"), out);
		} 
	}

	@Override
	public void onAccountWithDraw(String messageValue, ObjectOutputStream out) {
		logger.debug("Try to withdraw account");
		onAccountDeposit(messageValue, out);
	}

	/**
	 * Find specific client
	 * @param messageValue
	 * @throws ClientNotFoundException
	 */
	private Client findClient(String[] args) throws ClientNotFoundException {
		try {
			for (Client client: bank.getClients()){
				if (client.getName().equals(args[0])){
					return client;
				}
			}
			throw new ClientNotFoundException("Client not found");
		} catch (ArrayIndexOutOfBoundsException e){
			logger.error("Invalid message format");
			throw new IllegalArgumentException("Invalid command format");
		}
	}

}
